package com.szwistar.common.os;

import com.szwistar.common.os.TimerEx.TimingTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * 通用增强定时器
 * <p>实现了增强定时器功能，可用于大多数情形下对定时器的应用需求。
 * <p>它提供了两种底层实现：
 * <li>Timer0/TimerTask0: 是基于 java.util.Timer 的实现，在单线程中实现定时任务调度
 * <li>Timer1/TimerTask1: 是基于 ScheduledThreadPoolExecutor 的实现，在一个线程池中实现任务调度；它实际上已经超出了简单定时器的概念，是一个真正的多任务执行器！
 * <p>当前使用的是基于 ScheduledThreadPoolExecutor 的实现
 * @see TimerEx#manager()
 */
public class TimerEx {
	protected static final Logger LOG = LoggerFactory.getLogger(TimerEx.class);

	/**
	 * 定时任务接口
	 */
	public static interface TimingTask extends Runnable {
		public boolean cancel();
	}

	protected static TimingTaskManager _manager;
	protected static int _timerThreadPoolSize = 4;

	// !!! 任务工厂函数 !!!
	protected static TimingTaskManager manager() {
		if(_manager != null) { return _manager; }

		// 实现方案1：使用 Timer 实现
		//return _timer = new Timer0();

		// 实现方案2：使用 ScheduledThreadPoolExecutor 实现
		return _manager = new Timer1(_timerThreadPoolSize);
	}

	/**
	 * 设置定时器线程池大小
	 */
	public static void setTimerThreadPoolSize(int size) { _timerThreadPoolSize = size; }

	/**
	 * 停止所有任务
	 */
	public static void shutdown() {
		LOG.info("停止定时器任务 ...");
		if(_manager == null) { return; }
		_manager.shutdown();
		_manager = null;
	}

	protected static TimingTask createTask(Runnable callback, long repeat) {
		return (manager() instanceof Timer1) ? (new TimingTask1(callback, repeat)) : (new TimingTask0(callback, repeat));
	}

	protected static TimingTask createTask(Runnable callback) {
		return createTask(callback, 0);
	}

	/**
	 * 稍后在定时线程中执行
	 */
	public static TimingTask runLater(Runnable callback) {
		return runAfter(0, callback);
	}

	/**
	 * 延时执行
	 * @param delay 延时 ms
	 */
	public static TimingTask runAfter(long delay, Runnable callback) {
		return manager().schedule(createTask(callback), delay);
	}

	/**
	 * 间隔执行
	 * @param interval 间隔时间 ms
	 */
	public static TimingTask runEvery(long interval, Runnable callback) {
		return runEvery(interval, 0, -1, callback);
	}

	/**
	 * 延时间隔执行(永远循环)
	 * @param interval 间隔时间 ms
	 * @param delay 延时 ms
	 */
	public static TimingTask runEvery(long interval, long delay, Runnable callback) {
		return runEvery(interval, delay, -1, callback);
	}

	/**
	 * 延时间隔执行(循环 n 次)
	 * @param interval 间隔时间 ms
	 * @param delay 延时 ms
	 * @param repeat 重复次数: <0: 永远执行；=0: 不重复执行；>0 重复执行次数
	 */
	public static TimingTask runEvery(long interval, long delay, long repeat, Runnable callback) {
		return manager().scheduleAtFixedRate(createTask(callback, repeat), delay, interval);
	}

	/**
	 * 在今天的某个时间执行
	 * @param time 时间，格式为 "12:33:22"；如果时间已经过了，则不执行了
	 */
	public static TimingTask runAtToday(String time, Runnable callback) {
		try {
			DateTime date = DateTime.atToday(time);
			return runAt(date, callback);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 在某个日期的某个时间执行
	 * @param date 日期时间；如果时间已经过了，则不执行了
	 */
	public static TimingTask runAt(Date date, Runnable callback) {
		// 计算延时时间
    	long delay = date.getTime() - System.currentTimeMillis();
    	// 如果时间已经过了，则不执行了
    	if(delay < 0) { return null; }
    	// 延时执行
		return runAfter(delay, callback);
	}

	/**
	 * 在某个日期的某个时间执行
	 * @param date 日期时间，格式为 "2018-12-25 07:59:55"；如果时间已经过了，则不执行了
	 */
	public static TimingTask runAt(String date, Runnable callback) {
		try {
			return runAt(DateTime.at(date), callback);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 每天定点执行，永远循环
	 * @param time 时间，格式为 "12:33:22"；如果今天时间已经过了，则今天不执行了，到明天的那个时间再执行
	 */
	public static TimingTask runEverydayAt(String time, Runnable callback) {
		return runEverydayAt(time, -1, callback);
	}

	/**
	 * 每天定点执行，循环 n 次
	 * @param time 时间，格式为 "12:33:22"；如果今天时间已经过了，则今天不执行了，到明天的那个时间再执行
	 * @param repeat 重复次数: <0: 永远执行；=0: 不重复执行；>0 重复执行次数
	 */
	public static TimingTask runEverydayAt(String time, long repeat, Runnable callback) {
		try {
		    DateTime date = DateTime.atToday(time);
			// 计算第一次触发的时间
	    	long delay = date.getTime() - System.currentTimeMillis();
	    	// 如果今天已经过了指定的时间点，则要明天才能触发
		    if(delay < 0) { delay += 24*60*60*1000; }

			return manager().scheduleAtFixedRate(createTask(callback, repeat), delay, 24*60*60*1000);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 在每个星期的某天的某个时间点重复执行
	 * @param theDay 1~7，表示每个星期的第几天（周日=1, 周一=2; ...; 周六=7）
	 * @param time 时间点, 如"08:00:00"
	 * @param repeat 重复次数: <0: 永远执行；=0: 不重复执行；>0 重复执行次数
	 */
	public static TimingTask runEveryWeekdayAt(int theDay, String time, int repeat, Runnable callback) {
		try {
			SimpleDateFormat simpleDate = new SimpleDateFormat("HH:mm:ss");
			simpleDate.parse(time);
			Calendar time2 = simpleDate.getCalendar();

			// 计算第一次触发的日期时间
			Calendar due = Calendar.getInstance();
			due.set(Calendar.DAY_OF_WEEK, theDay);
			due.set(Calendar.HOUR, time2.get(Calendar.HOUR));
			due.set(Calendar.MINUTE, time2.get(Calendar.MINUTE));
			due.set(Calendar.SECOND, time2.get(Calendar.SECOND));
			due.set(Calendar.MILLISECOND, 0);
			// 如果本周目标时间已过，则移到下一周
			if(due.before(Calendar.getInstance())) { due.roll(Calendar.WEEK_OF_YEAR, 1); }

			// 计算第一次触发的时间
			long delay = due.getTimeInMillis() - System.currentTimeMillis();
			return manager().scheduleAtFixedRate(createTask(callback, repeat), delay, 7*24*60*60*1000);
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * 在每个月的某天的某个时间点重复执行
	 * @param theDay 1~31 或 -1~-31，每个月的第几天。如果>0，则表示从月初开始；如果<0，则表示从月末开始向前。
	 * @param time 时间点, 如"08:00:00"
	 * @param repeat 重复次数: <0: 永远执行；=0: 不重复执行；>0 重复执行次数
	 */
	public static TimingTask runEveryMonthdayAt(int theDay, String time, int repeat, Runnable callback) {
		try {
			SimpleDateFormat simpleDate = new SimpleDateFormat("HH:mm:ss");
			simpleDate.parse(time);
			Calendar time2 = simpleDate.getCalendar();

			// 计算第一次触发的日期时间
			Calendar due = Calendar.getInstance();
			// TODO 实现从月末开始向前
			//if(theDay <= 0) { theDay = due.get(Calendar.DAY_OF_MONTH) + 1 + theDay; }
			if(theDay <= 0) { throw new Exception("Invalid day in month"); }
			due.set(Calendar.DAY_OF_MONTH, theDay);
			due.set(Calendar.HOUR, time2.get(Calendar.HOUR));
			due.set(Calendar.MINUTE, time2.get(Calendar.MINUTE));
			due.set(Calendar.SECOND, time2.get(Calendar.SECOND));
			due.set(Calendar.MILLISECOND, 0);
			// 如果本月目标时间已过，则移到下一个月
			if(due.before(Calendar.getInstance())) { due.roll(Calendar.MONTH, 1); }

			// 计算第一次触发的时间
			long delay = due.getTimeInMillis() - System.currentTimeMillis();
			// FIXME 考虑每个月有不同的天数
			return manager().scheduleAtFixedRate(createTask(callback, repeat), delay, 7*24*60*60*1000);
		} catch (Exception e) {
			return null;
		}
	}

	// for test only
	public static void main(String[] args) throws Exception {
		runLater(new Runnable() {
			@Override
			public void run() {
				System.out.println("runLater()");
			}
		});

		runAfter(1000, new Runnable() {
			@Override
			public void run() {
				System.out.println("runAfter(1000)");
			}
		});

		final TimingTask task = runEvery(1130, 500, 5, new Runnable() {
			@Override
			public void run() {
				System.out.println("runEvery(1130, 500, 5)");
			}
		});

		final String time = DateTime.afterNow(3000).formatTime();
		runEverydayAt(time, 0, new Runnable() {
			@Override
			public void run() {
				System.out.println("runEverydayAt: " + time);
				// 撤消上一个任务
				task.cancel();
			}
		});

		final int weekday = Calendar.getInstance().get(Calendar.DAY_OF_WEEK);
		final String time2 = DateTime.afterNow(6000).formatTime();
		runEveryWeekdayAt(weekday, time2, 0, new Runnable() {
			@Override
			public void run() {
				System.out.printf("runEveryWeekdayAt: %d, %s\n", weekday, time2);
			}
		});

		final int monthday = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
		final String time3 = DateTime.afterNow(9000).formatTime();
		runEveryMonthdayAt(monthday, time3, 0, new Runnable() {
			@Override
			public void run() {
				System.out.printf("runEveryMonthdayAt: %d, %s\n", monthday, time3);
			}
		});

		System.out.println("waiting ...");
		for(;;) { Thread.sleep(1); }
	}
}

// 标准定时器接口
interface TimingTaskManager {
	public void shutdown();
	public TimingTask schedule(TimingTask task, long delay);
	public TimingTask scheduleAtFixedRate(TimingTask task, long delay, long period);
}

/**
 * 基于 Timer 的实现
 */
class Timer0 extends Timer implements TimingTaskManager {
	@Override
	public void shutdown() { cancel(); }

	@Override
	public TimingTask schedule(TimingTask task, long delay) {
		super.schedule((TimingTask0)task, delay);
		return task;
	}

	@Override
	public TimingTask scheduleAtFixedRate(TimingTask task, long delay, long period) {
		super.schedule((TimingTask0)task, delay, period);
		return task;
	}
}

/**
 * 基于 Timer 的任务
 */
class TimingTask0 extends TimerTask implements TimingTask {
	protected static final Logger LOG = LoggerFactory.getLogger(TimingTask0.class);

	// 重复次数: <0: 永远执行；=0: 不重复执行；>0 重复执行次数
	long repeat = 0;
	// 回调接口
	Runnable callback;

	public TimingTask0(Runnable callback, long repeat) {
		super();
		if(callback == null) { throw new IllegalArgumentException("No callback !"); }
		this.callback = callback;
		this.repeat = repeat;
	}

	public TimingTask0(Runnable callback) {
		this(callback, 0);
	}

	@Override
	public void run() {
		try {
			// 必须捕获可能的异常！否则会导致整个 Timer 线程被中止！
			callback.run();
		} catch(Exception e) {
			LOG.error("执行定时任务失败！", e);
		}
		if(repeat > 0) { repeat--; }
		if(repeat == 0) { cancel(); }
	}
}

/**
 * 基于 ScheduledThreadPoolExecutor 的实现
 */
class Timer1 extends ScheduledThreadPoolExecutor implements TimingTaskManager {
	public Timer1(int corePoolSize) {
		super(corePoolSize);
	}

	@Override
	public TimingTask schedule(TimingTask task, long delay) {
		((TimingTask1)task).future = super.schedule(task, delay, TimeUnit.MILLISECONDS);
		return task;
	}

	@Override
	public TimingTask scheduleAtFixedRate(TimingTask task, long delay, long period) {
		((TimingTask1)task).future = super.scheduleAtFixedRate(task, delay, period, TimeUnit.MILLISECONDS);
		return task;
	}
}

/**
 * 基于 ScheduledThreadPoolExecutor 的任务
 */
class TimingTask1 implements TimingTask {
	protected static final Logger LOG = LoggerFactory.getLogger(TimingTask1.class);

	// 可撤销的对象
	ScheduledFuture<?> future;

	// 重复次数: <0: 永远执行；=0: 不重复执行；>0 重复执行次数
	long repeat = 0;
	// 回调接口
	Runnable callback;

	public TimingTask1(Runnable callback, long repeat) {
		super();
		if(callback == null) { throw new IllegalArgumentException("No callback !"); }
		this.callback = callback;
		this.repeat = repeat;
	}

	public TimingTask1(Runnable callback) {
		this(callback, 0);
	}

	@Override
	public boolean cancel() {
		return future.cancel(false);
	}

	@Override
	public void run() {
		try {
			// 必须捕获可能的异常！否则会导致整个 Timer 线程被中止！
			callback.run();
		} catch(Exception e) {
			LOG.error("执行定时任务失败！", e);
		}
		if(repeat > 0) { repeat--; }
		if(repeat == 0) { cancel(); }
	}
}
